package org.dftproject.genesis.ui.figures;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.draw2d.AbstractHintLayout;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.Rectangle;

public class ColumnLayout extends AbstractHintLayout {

	protected Map<IFigure, Object> constraints = new HashMap<IFigure, Object>();

	protected int spacing = 0;
	
	public ColumnLayout() {
	}
	
	public ColumnLayout(int spacing) {
		this.spacing = spacing;
	}

	@Override
	public Object getConstraint(IFigure child) {
		return constraints.get(child);
	}

	@Override
	public void setConstraint(IFigure child, Object constraint) {
		super.setConstraint(child, constraint);
		if (constraint != null)
			constraints.put(child, constraint);
	}

	public ColumnData getColumnData(IFigure child) {
		Object constraint = getConstraint(child);
		if (constraint instanceof ColumnData)
			return (ColumnData) constraint;
		return null;
	}

	@SuppressWarnings("unchecked")
	protected List<IFigure> getConstrainedChildren(IFigure container) {
		List<IFigure> children = container.getChildren();
		List<IFigure> constrainedChildren = new ArrayList<IFigure>();

		for (IFigure child : children) {
			if (!child.isVisible())
				continue;
			ColumnData data = getColumnData(child);
			if (data == null)
				continue;
			constrainedChildren.add(child);
		}

		return constrainedChildren;
	}

	protected Dimension calculateChildrenSize(List<IFigure> children, int wHint, int hHint, boolean preferred) {
		int width = 0;
		int height = 0;

		if (wHint >= 0) {
			// Get the total weight

			int totalWeight = 0;
			for (IFigure child : children)
				totalWeight += getColumnData(child).getWeight();

			// Subtract spacing

			width = wHint;
			wHint -= Math.max(0, children.size() - 1) * spacing;

			// Calculate children width and height
			
			int childCount = children.size();
			Dimension[] childSizes = new Dimension[childCount];
			
			for (int i = 0; i < childCount; i++) {
				IFigure child = children.get(i);
				if (getColumnData(child).getWeight() != 0)
					continue;

				childSizes[i] = child.getPreferredSize(wHint, hHint);
				wHint -= childSizes[i].width;
				height = Math.max(height, childSizes[i].height);
			}
			
			for (int i = 0; i < childCount; i++) {
				if (childSizes[i] != null)
					continue;
				
				IFigure child = children.get(i);
				
				int allocatedWidth = (int) (wHint * (double) getColumnData(child).getWeight() / totalWeight);

				childSizes[i] = preferred ? child.getPreferredSize(allocatedWidth, hHint) : child.getMinimumSize(allocatedWidth, hHint);
				height = Math.max(height, childSizes[i].height);
			}
		} else {
			// Calculate children width and height

			for (IFigure child : children) {
				Dimension childSize = preferred ? child.getPreferredSize(wHint, hHint) : child.getMinimumSize(wHint, hHint);
				width += childSize.width;
				height = Math.max(height, childSize.height);
			}
		}

		return new Dimension(width, height);
	}

	@SuppressWarnings("unchecked")
	@Override
	protected Dimension calculatePreferredSize(IFigure container, int wHint, int hHint) {
		Insets insets = container.getInsets();
		if (hHint >= 0)
			hHint = Math.max(0, hHint - insets.getHeight());
		if (wHint >= 0)
			wHint = Math.max(0, wHint - insets.getWidth());

		List<IFigure> children = getConstrainedChildren(container);
		Dimension preferredSize = calculateChildrenSize(children, wHint, hHint, true);
		if (wHint >= 0 && preferredSize.width > wHint)
			preferredSize = calculateChildrenSize(children, preferredSize.width, hHint, true);

		return preferredSize.expand(insets.getWidth(), insets.getHeight()).union(getBorderPreferredSize(container));
	}

	@SuppressWarnings("unchecked")
	public void layout(IFigure parent) {
		List<IFigure> children = getConstrainedChildren(parent);
		
		Rectangle clientArea = parent.getClientArea();
		
		int width = clientArea.width;
		int height = clientArea.width;
		
		// Get the total weight

		int totalWeight = 0;
		for (IFigure child : children)
			totalWeight += getColumnData(child).getWeight();

		// Subtract spacing

		width -= Math.max(0, children.size() - 1) * spacing;

		// Calculate children sizes
		
		int childCount = children.size();
		Dimension[] childSizes = new Dimension[childCount];
		
		for (int i = 0; i < childCount; i++) {
			IFigure child = children.get(i);
			if (getColumnData(child).getWeight() != 0)
				continue;

			childSizes[i] = child.getPreferredSize(width, height);
			width -= childSizes[i].width;
		}
		
		for (int i = 0; i < childCount; i++) {
			if (childSizes[i] != null)
				continue;
			
			IFigure child = children.get(i);
			
			int allocatedWidth = (int) (width * (double) getColumnData(child).getWeight() / totalWeight);

			Dimension childSize = child.getPreferredSize(allocatedWidth, height);
			childSize.width = Math.max(childSize.width, allocatedWidth);
			childSizes[i] = new Dimension(childSize.width, childSize.height);
		}
		
		// Place children
		
		int x = clientArea.x;
		int y = clientArea.y;
		for (int i = 0; i < childCount; i++) {
			IFigure child = children.get(i);
			child.setBounds(new Rectangle(x, y, childSizes[i].width, childSizes[i].height));
			
			x += childSizes[i].width + spacing;
		}
	}

	@Override
	protected boolean isSensitiveVertically(IFigure container) {
		return false;
	}

	public int getSpacing() {
		return spacing;
	}

	public void setSpacing(int spacing) {
		if (this.spacing != spacing) {
			this.spacing = spacing;
			invalidate();
		}
	}

}
